from time import time

from khronos.utils import Call, UNDEF
from khronos.statistics.ftable import FTable
from khronos.statistics.tally import Tally
from khronos.statistics.tseries import TSeries

class AutoStat(object):
    """Descriptor class for automatic statistic collectors. This class can be used to create 
    attribute descriptors which may be treated as regular attributes, but collect data (into
    frequency tables, tallies, or time series) when assigned to.
    Function prototypes:
        constructor()               -> stat        # statistic constructor
        collector(obj, stat, value) -> (void)      # statistic collector
    """
    def __init__(self, constructor=None, collector=None):
        self.__constructor = constructor
        self.__collector   = collector
        self.__value       = {None: self}
        self.__stat        = {}
        
    # FUNCTION DECORATORS
    def constructor(self, fnc):
        self.__constructor = fnc
        return fnc
        
    def collector(self, fnc):
        self.__collector = fnc
        return fnc
        
    # DESCRIPTOR PROTOCOL
    def __get__(self, obj, owner):
        try:
            return self.__value[obj]
        except KeyError:
            raise AttributeError("uninitialized attribute")
            
    def __set__(self, obj, value):
        self.__value[obj] = value
        self.__collector(obj, self.__stat[obj], value)
        
    def __delete__(self, obj):
        try:
            del self.__value[obj]
            del self.__stat[obj]
        except KeyError:
            raise AttributeError("uninitialized attribute")
            
    # NAMED METHODS
    get    = __get__
    set    = __set__
    delete = __delete__
    
    def initialize(self, obj, value, stat=None, collect=True):
        self.__value[obj] = value
        self.__stat[obj] = stat if stat is not None else self.__constructor()
        if collect:
            self.__collector(obj, self.__stat[obj], value)
            
    def stat(self, obj):
        """Access the statistic collector (FTable, Tally, or TSeries) object stored by the 
        descriptor. This can be useful to issue commands to the statistic collector, like 
        plotting graphs or computing statistics."""
        return self.__stat[obj]
        
    # FACTORY FUNCTIONS
    # The functions below are factory functions created to easily make AutoStats for the three 
    # basic types of statistic collectors available (FTable, Tally, and TSeries). All the user 
    # needs to do is specify the getter and setter functions afterwards using the decorators.
    @staticmethod
    def FTable(*args, **kwargs):
        return AutoStat(Call(FTable, *args, **kwargs), default_collect)
        
    @staticmethod
    def Tally(*args, **kwargs):
        return AutoStat(Call(Tally, *args, **kwargs), default_collect)
        
    @staticmethod
    def TSeries(*args, **kwargs):
        return AutoStat(Call(TSeries, *args, **kwargs), default_collect)
        
def default_collect(obj, stat, value):
    stat.collect(value)
    
def test_tseries():
    class Clock(object):
        def __init__(self):
            self.value = 0
            
        def __call__(self):
            t = self.value
            self.value += 1
            return t
            
    class A(object):
        x = AutoStat.TSeries(storing=True, time_fnc=Clock())
        def __init__(self):
            A.x.initialize(self, 0)
            
    a = A()
    for x in range(1, 11) + range(11, -11, -2):
        a.x = x
    A.x.stat(a).run_chart(ylabel="Stock value")
    
